package com.progenies.ecg.asm31.model.codeparts.lines;

import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

import com.progenies.ecg.asm31.util.BytecodeInstructionsUtils;
import com.progenies.ecg.asm31.util.VariableRegister.Variable;
import com.progenies.ecg.model.ValueReference;



/**
 * Define an instruction to create a local variable.
 * 
 * It can receive an initial value (optional)
 * 
 * @author ofc587a87
 * 
 *
 */
public class CreateLocalVariableLineCodePart<T extends Object> extends ConfigurableLineCodePart
{
	private String name;
	private Class<T> classType;
	private T value;
	private boolean forcedNull;
	private boolean initialize;
	private int[] arraySize;
	private ValueReference<?> varReference;
	private ValueReference<?>[] arrVarReference;
	private ValueReference<?>[][] mtxVarReference;

	
	CreateLocalVariableLineCodePart(String name, Class<T> classType)
	{
		this.name=name;
		this.classType=classType;
		this.forcedNull=false;
	}
	
	CreateLocalVariableLineCodePart(String name, Class<T> classType, ValueReference<?> varReference)
	{
		this(name, classType);
		this.varReference=varReference;
	}
	
	CreateLocalVariableLineCodePart(String name, Class<T> classType, boolean initialize)
	{
		this(name, classType, (int[])null, initialize);
	}

	CreateLocalVariableLineCodePart(String name, Class<T> classType, int arraySize[], boolean initialize)
	{
		this(name, classType);
		this.arraySize=arraySize;
		this.initialize=initialize;
		this.forcedNull=false;
	}


	CreateLocalVariableLineCodePart(String name, Class<T> classType, T value)
	{
		this(name, classType);
		this.value=value;
		this.forcedNull=value==null;
	}
	
	CreateLocalVariableLineCodePart(String name, Class<T> classType, ValueReference<?>[] arrVarReference)
	{
		this(name, classType);
		this.arrVarReference=arrVarReference;
	}
	
	CreateLocalVariableLineCodePart(String name, Class<T> classType, ValueReference<?>[][] mtxVarReference)
	{
		this(name, classType);
		this.mtxVarReference=mtxVarReference;
	}
	
	
	@Override
	public void generateBytecode() {
		
		if(this.visitor==null )
			throw new IllegalStateException("LinePart has not been configurated, please use ClassGenerator");
		
//		System.out.println("Registering variable "+name);
		//creates the new object
		int index=varRegister.registerLocalVariable(name, Type.getType(classType));
		
		//start and end labels
		Label start=BytecodeInstructionsUtils.createAndVisitLabel(visitor);
		Label end=BytecodeInstructionsUtils.createAndVisitLabel(visitor);
		visitor.visitLocalVariable(name, Type.getDescriptor(classType), null, start, end, index);
		
		//if a value must be stored
		if(this.value!=null || varReference!=null || arrVarReference!=null || mtxVarReference!=null)
		{
			Variable var=varRegister.getVariable(name);
			
			if(varReference!=null) // copy value from other variable
			{
				BytecodeInstructionsUtils.insertLocalVariableIntoStack(visitor, varRegister, varReference.getName());
			}
			else
			{
				//if the value is an array, i'll need to create a new one into the stack and populate it
				if(var.getType().getSort() == Type.ARRAY)
				{
			
					if(value!=null)
						BytecodeInstructionsUtils.createNewArrayIntoStackAndPopulate(visitor, var.getType(), value);
					else if(arrVarReference!=null)
						BytecodeInstructionsUtils.createNewArrayIntoStackAndPopulate(visitor, var.getType(), arrVarReference, getParent(), varRegister);
					else if(mtxVarReference!=null)
						BytecodeInstructionsUtils.createNewArrayIntoStackAndPopulate(visitor, var.getType(), mtxVarReference, getParent(), varRegister);
					else // The desired value is NULL
						visitor.visitInsn(Opcodes.ACONST_NULL);
				}
				else
				{
					if(value!=null)
					{
						//check if the assignment is possible
						if(this.classType.isPrimitive() == value.getClass().isPrimitive()) //Both of them are or aren't primitive classes
						{
							if(!this.classType.isAssignableFrom(value.getClass()))
								throw new RuntimeException("Type "+value.getClass().getName()+" is not assignable to "+this.classType.getName());
						}
						// if only one of them is primitive, the JVM will autounbox it
						BytecodeInstructionsUtils.insertValueIntoStack(visitor, value);
					}
					else
						throw new RuntimeException("Several Variable References found, but the variable type is not an array!");
				}
			}
			visitor.visitVarInsn(var.getType().getOpcode(Opcodes.ISTORE), var.getIndex());
		}
		else if(value==null && forcedNull) 
		{
			Variable var=varRegister.getVariable(name);
			
			//Check if the value accepts null (just Objects or Arrays
			if(var.getType().getSort()!=Type.OBJECT && var.getType().getSort()!=Type.ARRAY)
				throw new RuntimeException("The variable type is primitive or array, can't accept null values");
			
			visitor.visitInsn(Opcodes.ACONST_NULL);
			visitor.visitVarInsn(var.getType().getOpcode(Opcodes.ISTORE), var.getIndex());
		}
		else if(initialize)
		{
			Variable var=varRegister.getVariable(name);
			
			if(var.getType().getSort() == Type.ARRAY)
			{
				//creates new empty array and store in stack
				BytecodeInstructionsUtils.createNewArrayIntoStack(visitor, var.getType(), arraySize);
			}
			else if(var.getType().getSort() == Type.OBJECT)
			{
				//creates new object and store in stack
				BytecodeInstructionsUtils.createNewObjectIntoStack(visitor, var.getType(), null, varRegister); //default constructor, no parameters
			}
			else //primitive, can't be initialized!!
			{
				throw new RuntimeException("Type "+var.getType().getClassName()+" can't be initialized");
			}

			//store stack object/array into local variable
			visitor.visitVarInsn(var.getType().getOpcode(Opcodes.ISTORE), var.getIndex());
		}
		
//		System.out.println("END Registering variable "+name);
	}


	
	
	

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Class<T> getClassType() {
		return classType;
	}

	public void setClassType(Class<T> classType) {
		this.classType = classType;
	}

	public T getValue() {
		return value;
	}

	public void setValue(T value) {
		this.value = value;
	}

	public void setInitialize(boolean b) {
		this.initialize=b;
	}





}
